//
//  BBFilteredDetector.m
//  BBTouch
//
//  Created by ben smith on 27/09/08.
//  This file is part of BBTouch.
//
//  BBTouch is free software: you can redistribute it and/or modify
//  it under the terms of the GNU Lesser General Public License as published by
//  the Free Software Foundation, either version 3 of the License, or
//  (at your option) any later version.

//  BBTouch is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//  GNU Lesser General Public License for more details.

//  You should have received a copy of the GNU Lesser General Public License
//  along with BBTouch.  If not, see <http://www.gnu.org/licenses/>.
// 
//  Copyright 2008 Ben Britten Smith ben@benbritten.com .
//

#import "BBFilteredDetector.h"
#import "BBImagePreProcessor.h"
#import <OpenCV/OpenCV.h>

@implementation BBFilteredDetector

@synthesize highpassValue;
@synthesize noiseValue;
@synthesize threshValue;
@synthesize threshSize;

@synthesize finalImage;
@synthesize highPassImage;
@synthesize noiseReduxImage;
@synthesize bgSubtractionImage;

- (id) init
{
	self = [super init];
	if (self != nil) {
		self.highpassValue = 19;
		self.noiseValue = 7;
		self.threshSize = 15;
		self.threshValue = -2;
	}
	return self;
}

-(void)analyze:(NSImage*)anImage
{
	if (bgImage == nil) return; // cant do anything without a background image
	
	NSBitmapImageRep * bitRep = [[anImage representations] objectAtIndex:0];

	srcBytes = [bitRep bitmapData];
	cvSetData(srcIpl, srcBytes, bytesPerRow);

	cvSetImageROI(srcIpl, cvROI);
	
	[self filterImage];
	
	NSBitmapImageRep * finalRep = [[self.finalImage representations] objectAtIndex:0];

	[BBImagePreProcessor borderROI:finalRep roi:roi colorValue:0];
	
	[self analyzeRawData];
}

-(void)filterImage
{
	// INVERT??!!
	
	// run my filters
	// 0) background subtract
	// 1) high pass (a blur and a subtraction)
	// 2) noise reduction (a blur)
	// 3) thresholding
	// first subtract the bacground form the source, put the result in the buffer
	if (invert) {
		cvSub(bgIpl, srcIpl, bgSubtractionIpl,0);
	} else {
		cvSub(srcIpl, bgIpl, bgSubtractionIpl,0);		
	}
	
	// now for the highpass, first a blur, then a subtract
	// smooth the bgsubtraction image place the result into the final image (as a buffer basically)
	cvSmooth(bgSubtractionIpl, finalIpl, CV_BLUR, highpassValue, highpassValue, 0, 0);
	// now, subtract the blurred final image from the original and put it into the highpass buffer

	cvSub(bgSubtractionIpl, finalIpl, highpassIpl,0);		
	
	// now for the noise reduction
	// just a simple blur
	cvSmooth(highpassIpl, noiseIpl, CV_BLUR, noiseValue, noiseValue, 0, 0);
	
	// finally a nice adaptive threshold
	cvAdaptiveThreshold(noiseIpl, finalIpl, 250, CV_THRESH_BINARY, CV_ADAPTIVE_THRESH_MEAN_C, threshSize, threshValue);
//	NSLog(@"this is happening %d %d %d",finalBytes[1234],finalBytes[1235],finalBytes[1236]);
}

-(BOOL)validBlobPixel:(int)position
{
	// note, if you change this then you should also change scanSrc
	// they both have the check for 'goodness'
	// this double-code is for performance	
	if (( position < 0 ) || (position >= maxDataPosition)) {
		NSLog(@"position Out of bounds %d",position);
		return NO;
	}
	if (finalBytes[position] > 0) {
		return YES;
	}
	return NO;
}


-(void)scanSrcDataWithROI
{
	// run it like a state machine, set the state variables, then call the right functions
	// nested for loops; to scan through the entire image
	labelIndex = 1; // the labeling number for our blobs
	for (loopY = yMin; loopY < yMax; loopY++) {
		for (loopX = xMin; loopX < xMax; loopX++) {
			loopXYDataPosition = (loopY * bytesPerRow) + (loopX * samplesPerPixel);
			// the vast majority of pixels will be 'bad' ones and those just need to be marked
			// and move on
			// note, if you change this then you should also change validBlobPixel:
			if (finalBytes[loopXYDataPosition] == 0) {
				// bad move on
				labels[(loopY * pixelsWide) + loopX] = -1;
				continue;
			}
	//		NSLog(@"good: %d,%d %d",loopX,loopY,finalBytes[loopXYDataPosition]);
			// some are 'good' pixels
			[self handleGoodPixel];
		}
	}	
}

-(void)setBgImage:(NSImage*)anImage
{
	// sets the background image that gets subtracted from the foreground
	// i can use the first object in the reps because I know the cam is only adding a 
	// single rep. Altho this does work in the general case, usually
	NSBitmapImageRep * bitRep = [[anImage representations] objectAtIndex:0]; 
	
	// set up all the state vars here
	samplesPerPixel = [bitRep samplesPerPixel];
	bytesPerRow = [bitRep bytesPerRow];
	pixelsHigh = [bitRep pixelsHigh];
	pixelsWide = [bitRep pixelsWide];
	bgBytes = [bitRep bitmapData];
	maxDataPosition = pixelsHigh * bytesPerRow;
	
	bgIpl = cvCreateImage(cvSize(pixelsWide, pixelsHigh), IPL_DEPTH_8U, samplesPerPixel);
	cvSetData(bgIpl, bgBytes, bytesPerRow);
	
	srcIpl = cvCreateImage(cvSize(pixelsWide, pixelsHigh), IPL_DEPTH_8U, samplesPerPixel);
	
	self.finalImage = anImage; // makes a copy
	NSBitmapImageRep * finalRep = [[self.finalImage representations] objectAtIndex:0];
	finalBytes = [finalRep bitmapData];
	finalIpl = cvCreateImage(cvSize(pixelsWide, pixelsHigh), IPL_DEPTH_8U, samplesPerPixel);
	cvSetData(finalIpl, finalBytes, bytesPerRow);
	
	self.bgSubtractionImage = anImage; // makes a copy
	NSBitmapImageRep * subRep = [[self.bgSubtractionImage representations] objectAtIndex:0];
	bgSubtractionIpl = cvCreateImage(cvSize(pixelsWide, pixelsHigh), IPL_DEPTH_8U, samplesPerPixel);
	cvSetData(bgSubtractionIpl, [subRep bitmapData], bytesPerRow);

	self.highPassImage = anImage; // makes a copy
	NSBitmapImageRep * hpRep = [[self.highPassImage representations] objectAtIndex:0];
	highpassIpl = cvCreateImage(cvSize(pixelsWide, pixelsHigh), IPL_DEPTH_8U, samplesPerPixel);
	cvSetData(highpassIpl, [hpRep bitmapData], bytesPerRow);

	self.noiseReduxImage = anImage; // makes a copy
	NSBitmapImageRep * nrRep = [[self.noiseReduxImage representations] objectAtIndex:0];
	noiseIpl = cvCreateImage(cvSize(pixelsWide, pixelsHigh), IPL_DEPTH_8U, samplesPerPixel);
	cvSetData(noiseIpl, [nrRep bitmapData], bytesPerRow);
	
	
	// this should only need to be done once for any given image size	
	// but for now we do it every time we set the BG for simplicity sake
	[self setupNeighborGrid];
	[self setupStateArrays];
	// bitmapreps were never meant to be stored, so we will store the image instead
	[anImage retain];
	[bgImage release];
	bgImage = anImage;
	
	[self setROI:roi];
}


-(void)setROI:(NSRect)aRect
{
	// cache some useful infor about the ROI so i dont need to do it every time
	// saves a very small amount of cycles, possibly not worth it
	roi = aRect;
	yMin = NSMinY(roi);
	yMax = NSMaxY(roi);
	xMax = NSMaxX(roi);
	xMin = NSMinX(roi);
	
	cvROI = cvRect(xMin, yMin, NSWidth(roi), NSHeight(roi));
	if (bgImage != nil) {
		cvSetImageROI(bgIpl, cvROI);
		cvSetImageROI(bgSubtractionIpl, cvROI);
		cvSetImageROI(finalIpl, cvROI);
		cvSetImageROI(noiseIpl, cvROI);
		cvSetImageROI(highpassIpl, cvROI);
	}
}


@end
